package requests

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/textproto"
	uurl "net/url"
	"reflect"
	"strings"
	"sync"
	"time"

	xtls "github.com/refraction-networking/utls"

	"gitee.com/baixudong/gospider/bs4"
	"gitee.com/baixudong/gospider/proxy"
	"gitee.com/baixudong/gospider/tools"

	"github.com/tidwall/gjson"
	"golang.org/x/net/publicsuffix"
)

var UserAgent = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4929.0 Safari/537.36"

// 请求操作========================================================================= start
var headersMap = map[string]string{
	"Accept-encoding": "gzip, deflate, br",
	"Accept":          "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
	"Accept-Language": "zh-CN,zh;q=0.9",
	"User-Agent":      UserAgent,
}

type myInt int

const (
	keyPrincipalID myInt = iota
)

type reqCtxData struct {
	proxy       *uurl.URL
	redirectNum int
}
type File struct {
	Key     string //字段的key
	Name    string //文件名
	Content []byte //文件的内容
	Type    string //文件类型
}
type RequestOption struct {
	Method        string
	Url           string            //基础url
	DisProxy      bool              //关闭代理
	Proxy         string            //代理,http,https,socks5
	Timeout       int64             //请求超时时间
	Headers       map[string]string //请求头
	Cookies       []*http.Cookie    // cookies
	Files         []File            //文件
	Params        any               //url params
	Form          any               //multipart/form-data
	Data          any               //application/x-www-form-urlencoded
	Body          *bytes.Reader
	Json          any //application/json
	Text          any //text/xml
	TempData      any //临时变量
	Bytes         []byte
	DisCookie     bool                                      //关闭cookies管理
	DisDecode     bool                                      //关闭自动解码
	Bar           bool                                      //是否开启bar
	TryNum        int64                                     //重试次数
	CurTryNum     int64                                     //当前尝试次数
	BeforCallBack func(*RequestOption)                      //请求之前回调
	AfterCallBack func(*RequestOption, *Response) *Response //请求之后回调
	GetProxy      func() (string, error)                    //获取代理的方法,http,https,socks5
	RedirectNum   int                                       //重定向次数
	DisAlive      bool                                      //关闭长连接
	DisRead       bool                                      //关闭默认读取请求体
	DisUnZip      bool                                      //变比自动解压
	Err           error                                     //请求过程中的error
	converUrl     string
	contentType   string
}
type Client struct {
	RedirectNum   int                                       //重定向次数
	DisDecode     bool                                      //关闭自动编码
	DisRead       bool                                      //关闭默认读取请求体
	DisUnZip      bool                                      //变比自动解压
	TryNum        int64                                     //重试次数
	BeforCallBack func(*RequestOption)                      //请求前回调的方法
	AfterCallBack func(*RequestOption, *Response) *Response //请求后回调的方法
	Timeout       int64                                     //请求超时时间
	Proxy         string                                    //代理,http,https,socks5
	GetProxy      func() (string, error)                    //获取代理的方法,http,https,socks5
	Headers       map[string]string                         //请求头
	Bar           bool                                      //是否开启bar

	disCookie     bool          //关闭cookies管理
	disAlive      bool          //关闭长连接
	dialer        *net.Dialer   //dial
	proxyCli      *proxy.Client //代理服务
	client        *http.Client
	baseTransport *http.Transport
	mainTransport *http.Transport
	jar           *cookiejar.Jar
	checkRedirect func(req *http.Request, via []*http.Request) error
	ctx           context.Context
	cnl           context.CancelFunc
}
type ClientOption struct {
	TLSHandshakeTimeout   int64  //tls 超时时间,default:8
	ResponseHeaderTimeout int64  //第一个response headers 接收超时时间,default:8
	DisCookie             bool   //关闭cookies管理
	DisAlive              bool   //关闭长连接
	DisCompression        bool   //关闭请求头中的压缩功能
	DisHttp2              bool   //关闭http2
	LocalAddr             string //本地网卡出口ip
	IdleConnTimeout       int64  //空闲连接在连接池中的超时时间,default:20
	KeepAlive             int64  //keepalive保活检测定时,default:10
	DnsCacheTime          int64  //dns解析缓存时间60*30
	Ja3                   bool   //是否开启ja3
}

type Response struct {
	Response        *http.Response
	readOk          bool
	content         []byte
	encoding        string
	disDecode       bool
	disUnzip        bool
	contentType     []string
	contentEncoding []string
	data            map[string]any
}
type dialClient struct {
	ctx        context.Context
	dialer     *net.Dialer
	saveData   sync.Map
	dnsTimeout int64
}
type msgClient struct {
	time int64
	addr string
}

func (obj *dialClient) addrToIp(addr string) string {
	val, ok := obj.saveData.Load(addr)
	if ok {
		msgdata := val.(msgClient)
		if time.Now().Unix()-msgdata.time < obj.dnsTimeout {
			return msgdata.addr
		}
	}
	host, port, err := net.SplitHostPort(addr)
	if err == nil {
		names, err := net.LookupIP(host)
		if err == nil && len(names) != 0 {
			addr = fmt.Sprintf("[%s]:%s", names[0].String(), port)
			obj.saveData.Store(addr, msgClient{
				time: time.Now().Unix(),
				addr: addr,
			})
		}
	}
	return addr
}
func (obj *dialClient) dialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
	return obj.dialer.DialContext(ctx, network, obj.addrToIp(addr))
}

func (obj *dialClient) ja3DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
	con, err := obj.dialer.DialContext(ctx, network, obj.addrToIp(addr))
	if err != nil {
		return nil, err
	}
	// 根据地址获取host信息
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return nil, err
	}
	// 并且不验证host信息
	xtlsConf := &xtls.Config{
		ServerName:    host,
		Renegotiation: xtls.RenegotiateNever,
	}
	// 构建tls.UConn
	xtlsConn := xtls.UClient(con, xtlsConf, xtls.HelloCustom)
	clientHelloSpec := &xtls.ClientHelloSpec{
		TLSVersMax: tls.VersionTLS12,
		TLSVersMin: tls.VersionTLS10,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
			tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
			2333,
		},
		CompressionMethods: []byte{
			0,
		},
		Extensions: []xtls.TLSExtension{
			&xtls.RenegotiationInfoExtension{Renegotiation: xtls.RenegotiateOnceAsClient},
			&xtls.SNIExtension{ServerName: host},
			&xtls.UtlsExtendedMasterSecretExtension{},
			&xtls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []xtls.SignatureScheme{
				xtls.ECDSAWithP256AndSHA256,
				xtls.PSSWithSHA256,
				xtls.PKCS1WithSHA256,
				xtls.ECDSAWithP384AndSHA384,
				xtls.ECDSAWithSHA1,
				xtls.PSSWithSHA384,
				xtls.PSSWithSHA384,
				xtls.PKCS1WithSHA384,
				xtls.PSSWithSHA512,
				xtls.PKCS1WithSHA512,
				xtls.PKCS1WithSHA1}},
			&xtls.StatusRequestExtension{},
			&xtls.NPNExtension{},
			&xtls.SCTExtension{},
			&xtls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
			&xtls.SupportedPointsExtension{SupportedPoints: []byte{1}}, // uncompressed
			&xtls.SupportedCurvesExtension{
				Curves: []xtls.CurveID{
					xtls.X25519,
					xtls.CurveP256,
					xtls.CurveP384,
					xtls.CurveP521,
				},
			},
		},
	}
	// 定义hellomsg的加密套件等信息
	if xtlsConn.ApplyPreset(clientHelloSpec) != nil {
		return nil, err
	}
	// 握手
	if xtlsConn.Handshake() != nil {
		return nil, err
	}
	// fmt.Println("当前请求使用协议：", xtlsConn.HandshakeState.ServerHello.AlpnProtocol)
	return xtlsConn, err
}
func newBody(val any, valType string, dataMap map[string][]string) (*bytes.Reader, error) {
	switch value := val.(type) {
	case gjson.Result:
		switch valType {
		case "json", "text":
			return bytes.NewReader(tools.StringToBytes(value.Raw)), nil
		case "data", "params":
			tempVal := uurl.Values{}
			for kk, vv := range value.Map() {
				if vv.IsArray() {
					for _, v := range vv.Array() {
						tempVal.Add(kk, v.String())
					}
				} else {
					tempVal.Add(kk, vv.String())
				}
			}
			return bytes.NewReader(tools.StringToBytes(tempVal.Encode())), nil
		case "form":
			for kk, vv := range value.Map() {
				kkvv := []string{}
				if vv.IsArray() {
					for _, v := range vv.Array() {
						kkvv = append(kkvv, v.String())
					}
				} else {
					kkvv = append(kkvv, vv.String())
				}
				dataMap[kk] = kkvv
			}
			return nil, nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case map[string]string:
		switch valType {
		case "json", "text":
			return bytes.NewReader(tools.StringToBytes(tools.Any2json(value).Raw)), nil
		case "data", "params":

			tempVal := uurl.Values{}
			for kk, vv := range value {
				tempVal.Add(kk, vv)
			}

			return bytes.NewReader(tools.StringToBytes(tempVal.Encode())), nil

		case "form":
			for kk, vv := range value {
				dataMap[kk] = []string{vv}
			}
			return nil, nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case map[string][]string:
		switch valType {
		case "json", "text":
			return bytes.NewReader(tools.StringToBytes(tools.Any2json(value).Raw)), nil

		case "data", "params":

			tempVal := uurl.Values{}
			for kk, vvs := range value {
				for _, vv := range vvs {
					tempVal.Add(kk, vv)
				}
			}
			return bytes.NewReader(tools.StringToBytes(tempVal.Encode())), nil
		case "form":
			for kk, vv := range value {
				dataMap[kk] = vv
			}
			return nil, nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case map[string]any:
		switch valType {
		case "json", "text":
			return bytes.NewReader(tools.StringToBytes(tools.Any2json(value).Raw)), nil
		case "data", "params":
			tempVal := uurl.Values{}
			valueJson := tools.Any2json(value)
			for kk, vvs := range valueJson.Map() {
				tempVal.Add(kk, vvs.String())
			}
			return bytes.NewReader(tools.StringToBytes(tempVal.Encode())), nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case string:
		switch valType {
		case "json", "text", "data":
			return bytes.NewReader(tools.StringToBytes(value)), nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case []byte:
		switch valType {
		case "json", "text", "data":
			return bytes.NewReader(value), nil
		default:
			return nil, errors.New("未知的content-type：" + valType)
		}
	case io.Reader:
		tempCon, err := io.ReadAll(value)
		return bytes.NewReader(tempCon), err
	default:
		if reflect.TypeOf(val).Kind() == reflect.Struct {
			value2 := tools.Any2json(val)
			switch valType {
			case "json", "text":
				return bytes.NewReader(tools.StringToBytes(value2.Raw)), nil
			case "data", "params":

				tempVal := uurl.Values{}
				for kk, vv := range value2.Map() {
					if vv.IsArray() {
						for _, v := range vv.Array() {
							tempVal.Add(kk, v.String())
						}
					} else {
						tempVal.Add(kk, vv.String())
					}
				}
				return bytes.NewReader(tools.StringToBytes(tempVal.Encode())), nil
			case "form":
				for kk, vv := range value2.Map() {
					kkvv := []string{}
					if vv.IsArray() {
						for _, v := range vv.Array() {
							kkvv = append(kkvv, v.String())
						}
					} else {
						kkvv = append(kkvv, vv.String())
					}
					dataMap[kk] = kkvv
				}
				return nil, nil
			default:
				return nil, errors.New("未知的content-type：" + valType)
			}
		} else {
			return nil, errors.New("body-type错误")
		}
	}
}
func (obj *RequestOption) newBody() error {
	obj.converUrl = obj.Url
	var err error
	if obj.Bytes != nil {
		obj.Body = bytes.NewReader(obj.Bytes)
	}
	if obj.Form != nil {
		tempBody := bytes.NewBuffer(nil)
		dataMap := map[string][]string{}
		obj.Body, err = newBody(obj.Form, "form", dataMap)
		if err != nil {
			return err
		}
		writer := multipart.NewWriter(tempBody)
		for key, vals := range dataMap {
			for _, val := range vals {
				err := writer.WriteField(key, val)
				if err != nil {
					return err
				}
			}
		}
		escapeQuotes := strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
		for _, file := range obj.Files {
			h := make(textproto.MIMEHeader)
			h.Set("Content-Disposition",
				fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
					escapeQuotes.Replace(file.Key), escapeQuotes.Replace(file.Name)))
			if file.Type == "" {
				h.Set("Content-Type", "application/octet-stream")
			} else {
				h.Set("Content-Type", file.Type)
			}
			wp, err := writer.CreatePart(h)
			if err != nil {
				return err
			}
			_, err = wp.Write(file.Content)
			if err != nil {
				return err
			}
		}
		err = writer.Close()
		if err != nil {
			return err
		}
		obj.contentType = writer.FormDataContentType()
		temCon, err := io.ReadAll(tempBody)
		if err != nil {
			return err
		}
		obj.Body = bytes.NewReader(temCon)
	}
	if obj.Data != nil {
		obj.Body, err = newBody(obj.Data, "data", nil)
		if err != nil {
			return err
		}
		obj.contentType = "application/x-www-form-urlencoded"
	}
	if obj.Json != nil {
		obj.Body, err = newBody(obj.Json, "json", nil)
		if err != nil {
			return err
		}
		obj.contentType = "application/json"
	}
	if obj.Text != nil {
		obj.Body, err = newBody(obj.Text, "text", nil)
		if err != nil {
			return err
		}
		obj.contentType = "text/plain"
	}
	if obj.Params != nil {

		tempParam, err := newBody(obj.Params, "params", nil)
		if err != nil {
			return err
		}
		con, err := io.ReadAll(tempParam)
		if err != nil {
			return err
		}
		pu, err := uurl.Parse(obj.Url)
		if err != nil {
			return err
		}
		if pu.Query() == nil {
			obj.converUrl = obj.Url + "?" + tools.BytesToString(con)
		} else {
			obj.converUrl = obj.Url + "&" + tools.BytesToString(con)
		}
	}
	return nil
}

func (obj *Client) MergeProxy(proxyTypes ...proxy.Type) error {
	if obj.GetProxy == nil {
		return errors.New("not found get Proxy for mergeProxy")
	}
	var err error
	obj.proxyCli, err = proxy.NewClient(obj.ctx, proxy.ClientOption{
		Host:   "127.0.0.1",
		Dialer: obj.dialer,
	})
	if err != nil {
		return err
	}
	if len(proxyTypes) > 0 {
		obj.proxyCli.Type = proxyTypes[0]
	}
	obj.proxyCli.GetProxy = obj.GetProxy
	go obj.proxyCli.Run()
	obj.Proxy = obj.proxyCli.Addr()
	switch obj.proxyCli.Type {
	case proxy.Http:
		obj.Proxy = "http://" + obj.Proxy
	case proxy.Socks5:
		obj.Proxy = "socks5://" + obj.Proxy
	}
	return obj.proxyCli.Err
}
func NewClient(preCtx context.Context, client_optinos ...ClientOption) *Client {
	if preCtx == nil {
		preCtx = context.TODO()
	}
	ctx, cnl := context.WithCancel(preCtx)
	var session_option ClientOption
	if len(client_optinos) > 0 {
		session_option = client_optinos[0]
	}
	if session_option.IdleConnTimeout == 0 {
		session_option.IdleConnTimeout = 20
	}
	if session_option.KeepAlive == 0 {
		session_option.KeepAlive = 10
	}
	if session_option.TLSHandshakeTimeout == 0 {
		session_option.TLSHandshakeTimeout = 8
	}
	if session_option.ResponseHeaderTimeout == 0 {
		session_option.ResponseHeaderTimeout = 8
	}
	if session_option.DnsCacheTime == 0 {
		session_option.DnsCacheTime = 60 * 30
	}
	var client http.Client
	var jar *cookiejar.Jar
	if !session_option.DisCookie {
		var err error
		jar, err = cookiejar.New(&cookiejar.Options{
			PublicSuffixList: publicsuffix.List,
		})
		if err != nil {
			fmt.Println("new cookiejar error")
			cnl()
			return nil
		}
		client.Jar = jar
	}
	dialCli := &dialClient{ctx: ctx, dnsTimeout: session_option.DnsCacheTime}
	dialCli.dialer = &net.Dialer{}
	if session_option.LocalAddr != "" {
		if !strings.Contains(session_option.LocalAddr, ":") {
			session_option.LocalAddr += ":0"
		}
		localaddr, err := net.ResolveTCPAddr("tcp", session_option.LocalAddr)
		if err != nil {
			fmt.Println("ResolveTCPAddr error")
			cnl()
			return nil
		}
		dialCli.dialer.LocalAddr = localaddr
	}
	if session_option.KeepAlive != 0 {
		dialCli.dialer.KeepAlive = time.Duration(session_option.KeepAlive) * time.Second //keepalive保活检测定时
	}
	dialCli.dialer.Timeout = time.Second * 8
	baseTransport := http.Transport{
		MaxIdleConns:          65535,
		MaxConnsPerHost:       65535,
		MaxIdleConnsPerHost:   65535,
		ProxyConnectHeader:    http.Header{"User-Agent": []string{UserAgent}},
		TLSHandshakeTimeout:   time.Second * time.Duration(session_option.TLSHandshakeTimeout),
		ResponseHeaderTimeout: time.Second * time.Duration(session_option.ResponseHeaderTimeout),
		DisableKeepAlives:     session_option.DisAlive,
		DisableCompression:    session_option.DisCompression,
		Proxy: func(req *http.Request) (*uurl.URL, error) {
			return req.Context().Value(keyPrincipalID).(reqCtxData).proxy, nil
		},
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		IdleConnTimeout: time.Duration(session_option.IdleConnTimeout) * time.Second, //空闲连接在连接池中的超时时间
	}
	if !session_option.DisHttp2 {
		baseTransport.ForceAttemptHTTP2 = true
	}
	if session_option.Ja3 {
		baseTransport.DialContext = dialCli.ja3DialContext
	} else {
		baseTransport.DialContext = dialCli.dialContext
	}
	mainTransport := baseTransport.Clone()
	client.Transport = mainTransport
	checkRedirect := func(req *http.Request, via []*http.Request) error {
		redirectNum := req.Context().Value(keyPrincipalID).(reqCtxData).redirectNum
		if redirectNum == 0 || redirectNum >= len(via) {
			return nil
		}
		return http.ErrUseLastResponse
	}
	client.CheckRedirect = checkRedirect
	return &Client{ctx: ctx, cnl: cnl, client: &client, dialer: dialCli.dialer, baseTransport: &baseTransport, mainTransport: mainTransport, jar: jar, checkRedirect: checkRedirect, disAlive: session_option.DisAlive, disCookie: session_option.DisCookie}
}

func (obj *Client) newRequestOption(option *RequestOption) {
	if option.TryNum == 0 {
		option.TryNum = obj.TryNum
	}
	if option.BeforCallBack == nil {
		option.BeforCallBack = obj.BeforCallBack
	}
	if option.AfterCallBack == nil {
		option.AfterCallBack = obj.AfterCallBack
	}
	if option.Headers == nil {
		if obj.Headers == nil {
			option.Headers = headersMap
		} else {
			option.Headers = obj.Headers
		}
	}
	if option.Proxy == "" {
		option.Proxy = obj.Proxy
	}
	if option.GetProxy == nil {
		option.GetProxy = obj.GetProxy
	}
	if !option.Bar {
		option.Bar = obj.Bar
	}
	if option.RedirectNum == 0 {
		option.RedirectNum = obj.RedirectNum
	}
	if option.Timeout == 0 {
		option.Timeout = obj.Timeout
	}
	if !option.DisAlive {
		option.DisAlive = obj.disAlive
	}
	if !option.DisCookie {
		option.DisCookie = obj.disCookie
	}
	if !option.DisDecode {
		option.DisDecode = obj.DisDecode
	}
	if !option.DisRead {
		option.DisRead = obj.DisRead
	}
	if !option.DisUnZip {
		option.DisUnZip = obj.DisUnZip
	}
}
func (obj *Client) Request(preCtx context.Context, method string, url string, options ...RequestOption) (*Response, error) {
	if obj == nil {
		return nil, errors.New("初始化client失败")
	}
	if preCtx == nil {
		preCtx = obj.ctx
	}
	var option RequestOption
	if len(options) > 0 {
		option = options[0]
	}
	var err error
	option.Method = method
	option.Url = url
	if option.Body != nil {
		option.TryNum = 0
	}
	obj.newRequestOption(&option)
	if option.BeforCallBack == nil {
		if err = option.newBody(); err != nil {
			return nil, err
		}
	}
	//开始请求
	var resp *Response
	var temp_try_num int64
reqFor:
	for temp_try_num = 0; temp_try_num <= option.TryNum; temp_try_num++ {
		select {
		case <-preCtx.Done():
			option.Err = preCtx.Err()
			break reqFor
		default:
			if option.BeforCallBack != nil {
				option.BeforCallBack(&option)
			}
			if option.BeforCallBack != nil || option.AfterCallBack != nil {
				obj.newRequestOption(&option)
				if err = option.newBody(); err != nil {
					return nil, err
				}
			}
			if option.Body != nil {
				option.Body.Seek(0, 0)
			}
			resp, option.Err = obj.tempRequest(preCtx, option)
			if option.AfterCallBack != nil {
				callBackRespon := option.AfterCallBack(&option, resp)
				if callBackRespon != nil && option.Err == nil {
					return callBackRespon, option.Err
				}
			} else if option.Err == nil {
				return resp, option.Err
			}
		}
	}
	if option.Err != nil {
		return resp, option.Err
	}
	return resp, errors.New("max try num")
}

func (obj *Client) newResponse(r *http.Response, request_option RequestOption) (*Response, error) {
	response := &Response{Response: r}
	if request_option.DisRead {
		return response, nil
	}
	response.contentEncoding = r.Header["Content-Encoding"]
	response.contentType = r.Header["Content-Type"]
	if request_option.DisUnZip || r.Uncompressed || len(response.contentEncoding) == 0 || response.contentEncoding[0] == "" {
		response.disUnzip = true
	}
	response.disDecode = request_option.DisDecode
	if request_option.Bar && r.ContentLength <= 0 {
		request_option.Bar = false
	}
	err := response.read(request_option.Bar)
	return response, err
}
func (obj *Client) getClient(ctx context.Context, request_option RequestOption) (*http.Client, error) {
	if request_option.DisAlive || request_option.DisCookie {
		temp_client := obj.clone(request_option.DisAlive, request_option.DisCookie)
		return temp_client, nil
	} else {
		return obj.client, nil
	}
}
func (obj *Client) tempRequest(ctx context.Context, request_option RequestOption) (*Response, error) {
	method := strings.ToUpper(request_option.Method)
	url := request_option.converUrl
	var reqs *http.Request
	var err error
	//创建request
	if request_option.Body != nil {
		reqs, err = http.NewRequest(method, url, request_option.Body)
	} else {
		reqs, err = http.NewRequest(method, url, nil)
	}
	if err != nil {
		return nil, err
	}
	//添加cookies
	if request_option.Cookies != nil {
		for _, vv := range request_option.Cookies {
			reqs.AddCookie(vv)
		}
	}
	//添加headers
	for kk, vv := range request_option.Headers {
		reqs.Header.Add(kk, vv)
	}
	if reqs.Header.Get("Content-type") == "" && request_option.contentType != "" {
		reqs.Header.Add("Content-Type", request_option.contentType)
	}
	var ctxData reqCtxData
	if !request_option.DisProxy {
		if request_option.Proxy != "" {
			tempProxy, err := uurl.Parse(request_option.Proxy)
			if err != nil {
				return nil, err
			}
			ctxData.proxy = tempProxy
		} else if request_option.GetProxy != nil {
			temp_proxies, err := request_option.GetProxy()
			if err != nil {
				return nil, err
			}
			tempProxy, err := uurl.Parse(temp_proxies)
			if err != nil {
				return nil, err
			}
			ctxData.proxy = tempProxy
		}
	}
	if request_option.RedirectNum != 0 { //重定向次数
		ctxData.redirectNum = request_option.RedirectNum
	}
	var cancel context.CancelFunc
	reqCtx := context.WithValue(ctx, keyPrincipalID, ctxData)

	if request_option.Timeout > 0 { //超时构造
		reqCtx, cancel = context.WithTimeout(reqCtx, time.Duration(request_option.Timeout)*time.Second)
	} else {
		reqCtx, cancel = context.WithCancel(reqCtx)
	}
	defer cancel()
	reqs = reqs.WithContext(reqCtx)
	if request_option.DisAlive {
		reqs.Close = true
	}
	client, err := obj.getClient(reqCtx, request_option)
	if err != nil {
		return nil, err
	}
	r, err := client.Do(reqs)
	if err != nil {
		return nil, errors.New("send request error: " + err.Error())
	}
	if request_option.DisAlive {
		r.Close = true
	}
	response, err := obj.newResponse(r, request_option)
	if err != nil {
		return response, errors.New("newResponse error:" + err.Error())
	}
	return response, nil
}
func (obj *Client) clone(disAlive, disCookie bool) *http.Client {
	cli := &http.Client{
		CheckRedirect: obj.checkRedirect,
	}
	if !disCookie && obj.jar != nil {
		cli.Jar = obj.jar
	}
	if !disAlive {
		cli.Transport = obj.mainTransport
	} else {
		cli.Transport = obj.baseTransport.Clone()
	}
	return cli
}
func (obj *Client) Reset() error {
	if obj.jar != nil {
		jar, err := cookiejar.New(&cookiejar.Options{
			PublicSuffixList: publicsuffix.List,
		})
		if err != nil {
			return err
		}
		obj.jar = jar
	}
	obj.client.Transport = obj.baseTransport.Clone()
	return nil
}
func (obj *Client) Close() {
	obj.client.CloseIdleConnections()
	if obj.proxyCli != nil {
		obj.proxyCli.Close()
	}
	obj.cnl()
}
func (obj *Client) Cookies(url string, cookies ...*http.Cookie) []*http.Cookie {
	u, err := uurl.Parse(url)
	if err != nil {
		return []*http.Cookie{}
	}
	if len(cookies) > 0 && obj.client.Jar != nil {
		if err == nil {
			obj.client.Jar.SetCookies(u, cookies)
		} else {
			log.Print(url, ": setCookies error: ", err)
		}
	}
	return obj.client.Jar.Cookies(u)
}
func (obj *Client) ClearCookies() error {
	var jar *cookiejar.Jar
	var err error
	jar, err = cookiejar.New(&cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	})
	if err != nil {
		return err
	}
	obj.client.Jar = jar
	obj.jar = jar
	return nil
}
func (obj *Response) Location() (*uurl.URL, error) {
	return obj.Response.Location()
}
func (obj *Response) Cookies() []*http.Cookie {
	if obj.Response == nil {
		return []*http.Cookie{}
	}
	return obj.Response.Cookies()
}
func (obj *Response) StatusCode() int {
	if obj.Response == nil {
		return 0
	}
	return obj.Response.StatusCode
}
func (obj *Response) Url() *uurl.URL {
	if obj.Response == nil {
		return nil
	}
	return obj.Response.Request.URL
}
func (obj *Response) Headers() map[string][]string {
	if obj.Response == nil {
		return map[string][]string{}
	}
	return obj.Response.Header
}

func (obj *Response) Text(val ...string) string {
	if len(val) > 0 {
		obj.content = tools.StringToBytes(val[0])
	}
	return tools.BytesToString(obj.content)
}
func (obj *Response) Decode(encoding string) {
	if obj.encoding != encoding {
		obj.encoding = encoding
		obj.content = tools.Decode(obj.content, encoding)
	}
}
func (obj *Response) Map(path ...string) map[string]any {
	if obj.data != nil {
		return obj.data
	}
	if err := json.Unmarshal(obj.content, &obj.data); err != nil {
		return nil
	}
	return obj.data
}
func (obj *Response) Json(path ...string) gjson.Result {
	return tools.Any2json(obj.content, path...)
}
func (obj *Response) Content(val ...[]byte) []byte {
	if len(val) > 0 {
		obj.content = val[0]
	}
	return obj.content
}
func (obj *Response) Html() *bs4.Client {
	return bs4.NewClient(obj.Text(), obj.Url().String())
}

type barBody struct {
	body *bytes.Buffer
	bar  *tools.Bar
}

func (obj *barBody) Write(con []byte) (int, error) {
	l, err := obj.body.Write(con)
	obj.bar.Print(int64(l))
	return l, err
}
func (obj *Response) barRead() (*bytes.Buffer, error) {
	barData := &barBody{
		bar:  tools.NewBar(obj.Response.ContentLength),
		body: bytes.NewBuffer(nil),
	}
	_, err := io.Copy(barData, obj.Response.Body)
	if err != nil {
		return nil, err
	}
	return barData.body, nil
}
func (obj *Response) read(bar bool) error {
	//解压内容
	if obj.readOk {
		return nil
	}
	defer obj.Close()
	var bBody *bytes.Buffer
	var err error
	if bar {
		bBody, err = obj.barRead()
	} else {
		bBody = bytes.NewBuffer(nil)
		_, err = io.Copy(bBody, obj.Response.Body)
	}
	if err != nil {
		return errors.New("io.Copy error: " + err.Error())
	}
	if !obj.disUnzip {
		bBody, err = tools.ZipDecode(bBody, obj.contentEncoding...)
		if err != nil {
			return errors.New("gzip NewReader error: " + err.Error())
		}
	}
	obj.content = bBody.Bytes()
	if !obj.disDecode {
		obj.content, obj.encoding = tools.Charset(obj.content, obj.contentType...)
	}
	obj.readOk = true
	return nil
}
func (obj *Response) Close() error {
	io.Copy(io.Discard, obj.Response.Body)
	err := obj.Response.Body.Close()
	return err
}
